function r = symint(expr, varargin)
%SYMINT - 符号积分，支持不定积分、定积分.
%
% 语法：
%   r = symint(expr);                      - 对 symvar 得到的第一个符号变量求不定积分
%   r = symint(expr, var);                 - 对符号变量 var 求不定积分
%   r = symint(expr, a, b);                - 对 symvar 得到的第一个符号变量求定积分
%   r = symint(expr, var, a, b, varargin); - 支持的键值对包括 "Hold"、"PrincipalValue" 和 "IgnoreSpecialCases"
%       - "IgnoreAnalyticConstraints": 默认为 false, 忽略解析限制, 功能不完全, 不建议使用;
%       - "IgnoreSpecialCases": 默认为 false, 当 "IgnoreSpecialCases" 为 true 时, 忽略积分的特殊情况;
%       - "PrincipalValue": 默认为 false, 当 "PrincipalValue" 为 true 时, 计算广义积分的 Cauchy 主值;
%       - "Hold": 默认为 false, 当 "Hold" 为 true 时, 保持积分表达式.
%
% 示例:
% 不定积分
% >> [x, y] = syms("x y");
% >> symint(cos(x))
% sin(x)
% >> symint(log(x + y), x)
% x*log(x + y) - x + y*log(x + y)
%
% 定积分、重积分
% >> symint(exp(-x), 0, inf)
% 1
% >> symint(2*x, sin(y), cos(y))
% cos(2*y)
% >> symint(symint(exp(-x^2 - y^2), x, -inf, inf), y, -inf, inf)
% pi
%
% 矩阵元素的积分和忽略特殊情况
% >> M = sym(zeros(2, 2));
% >> M{0, 0} = exp(y);
% >> M{0, 1} = exp(x * y);
% >> M{1, 0} = sin(y);
% >> M{1, 1} = cos(x);
% >> symint(M, y)
% Matrix([[exp(y), Piecewise((exp(x*y)/x, (x > 0) | (x < 0)), (y, True))], [-cos(y), y*cos(x)]])
% >> symint(M, y, "IgnoreSpecialCases", true)
% Matrix([[exp(y), exp(x*y)/x], [-cos(y), y*cos(x)]])
%
% 保持积分号和计算 Cauchy 主值
% >> symint(1/(x - 1), x, 0, 2)
% nan
% >> symint(1/(x - 1), x, 0, 2, "Hold", true)
% Integral(1/(x - 1), (x, 0, 2))
% >> symint(1/(x - 1), x, 0, 2, "PrincipalValue", true)
% 0
%
% 忽略解析限制
% >> symint(asin(sin(x)), x, 0, 5)
% Integral(asin(sin(x)), (x, 0, 5))
% >> symint(asin(sin(x)), x, 0, 5, "IgnoreAnalyticConstraints", true)
% 25/2
%
    py = pyroot;
    sp = sympy_sp();
    % 获取表达式通过 symval 找到的第一个符号变量
    defvar = symvar(py.str(expr));
    if isempty(defvar)
        defvar = sym('x');
    else
        defvar = sym(defvar{1});
    end

    % % 如果 inputParser 解析无误, 删掉 parseParameters 函数使用这段代码即可
    % % 解析额外输入参数
    % p = inputParser;
    % p = addOptional(p, "var", defvar, @validFcn);
    % % a 和 b 的默认值设为 Python 的空列表，这在 SymPy 中相当于做不定积分
    % p = addOptional(p, "a", pylist, @validFcn);
    % p = addOptional(p, "b", pylist, @validFcn);
    % p = addParameter(p, "IgnoreAnalyticConstraints", false, @islogical);
    % p = addParameter(p, "IgnoreSpecialCases", false, @islogical);
    % p = addParameter(p, "PrincipalValue", false, @islogical);
    % p = addParameter(p, "Hold", false, @islogical);
    % p = parse(p, varargin{:});

    defaultVal = struct("var", defvar, "a", pylist, "b", pylist, "IgnoreAnalyticConstraints", false, "IgnoreSpecialCases", false, "PrincipalValue", false, "Hold", false);
    p = parseParameters(3, defaultVal, varargin{:});

    % 获取位置参数对应的值
    var = p.Results.var;
    a = p.Results.a;
    b = p.Results.b;

    % 忽略解析限制, 功能不完全
    if p.Results.IgnoreAnalyticConstraints
        expr = taylor(expr, defvar);
    end
    
    % 忽略 Cauchy 主值的情形: 不定积分、变限积分, 只有定积分才有 Cauchy 主值
    % 定积分: var 和 a 都是数值类型或者 a 和 b 都是数值类型
    if ~(isnumeric(var) && isnumeric(a)) && ~(isnumeric(a) && isnumeric(b)) 
        p.Results.PrincipalValue = false;
    end
    
    if ~ispylist(a) && ispylist(b)
        % int(expr, a, b) 情形
        r = sp.Integral(expr, pytuple(defvar, var, a));
    else
        % int(expr)、int(expr, val) 和 int(expr, val, a, b) 情形
        r = sp.Integral(expr, pytuple(var, a, b));
    end
    
    if p.Results.Hold
        r = r;
    elseif p.Results.PrincipalValue
        r = r.principal_value();
    elseif p.Results.IgnoreSpecialCases
        r = r.doit(pyargs("conds", "none"));
        r = r.simplify();
    else
        r = r.doit();
        r = r.simplify();
    end
end

function p = parseParameters(num, defaultVal, varargin)
% 代替 inputParser 的解析函数
    parameters = fieldnames(defaultVal);
    p = struct("Results", defaultVal);

    if isempty(varargin)
        return;
    end
    n = length(varargin);
    
    % 实际传入 Optional 参数个数
    numPotional = 0;
    % 默认规则：Optional 参数必须在 Name-Value 参数前面
    % 扫描所有参数 varargin, 找到第一个参数名, 前面的参数就是 Optional, 后面的参数就是 Name-Value
    for i = 1:n
        % 检查是否是有效的参数名
        if (ischar(varargin{i}) || isstring(varargin{i})) && any(strcmpi(varargin{i}, parameters))
            break;
        else
            numPotional = numPotional + 1;
        end
    end
    % Optional 参数个数最多为 num 个
    numPotional = min(numPotional, num);
    
    % 处理所有 Optional 参数
    for i = 1:numPotional
        field = parameters{i};
        val = varargin{i};

        if ~validFcn(val)
            error("不支持该类型参数值.")
        end

        p.Results.(field) = val;
    end
    
    % 处理所有 Name-Value 参数
    if mod(n - numPotional, 2) ~= 0
        error("所有参数都必须具有关联的值");
    end
    for i = numPotional+1:2:n
        ind = strcmpi(parameters, varargin{i});
            
        if any(ind)
            field = parameters{ind};
            val = varargin{i + 1};
            
            if ~islogical(val)
                msg = sprintf("参数 ""%s"" 不支持该类型值 ""%s"".", field, string(val));
                error(msg);
            end

            p.Results.(field) = val;
        else
            error(sprintf('无法识别的参数: %s', varargin{i}));
        end    
    end
end

function TF = validFcn(var)
% 判断变量 val 是否是 SymPy 的符号变量或者是北太天元的数值变量.
    py = pyroot;
    sp = sympy_sp;
    try
        % 符号和函数都是 Expr 类型
        TF = py.isinstance(var, sp.Expr);
    catch
        TF = false;
    end
    TF = TF || isnumeric(var);
end

function TF = ispylist(var)
% 判断是否为 Python 的 list 类型。
    py = pyroot;
    try
        TF = py.isinstance(var, py.list);
    catch
        TF = false;
    end
end
